﻿using Microsoft.AspNetCore.Hosting;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace ImgClip
{
    /// <summary>
    /// Stores local images
    /// </summary>
    public class LocalPocess : Pocess
    {
        private readonly static string WATERMARK_BASE_DIR = "watermark";
        private readonly static string IMAGE_BASE_DIR = "downloads";
        private readonly static ReaderWriterLockSlim writeLock = new ReaderWriterLockSlim();

        private IWebHostEnvironment _env;
        private ClipConfig _config;
        public LocalPocess(IWebHostEnvironment env,
            ClipConfig config)
        {
            _env = env;
            _config = config;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="dirName">dir name</param>
        /// <param name="name">file name</param>
        /// <returns></returns>
        public Stream Get(string dirName, string name)
        {
            var path = Path.Combine(GetRootPath(), dirName, name);
            return Read(path);
        }

        /// <summary>
        /// save file 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="stream"></param>
        /// <returns></returns>
        public Task SaveAsync(string dirName, string name, Stream stream)
        {
            return Task.Run(() =>
              {
                  string fullDir = Path.Combine(GetRootPath(), dirName);
                  if (!Directory.Exists(fullDir))
                      Directory.CreateDirectory(fullDir);
                  Write(Path.Combine(fullDir, name), stream);
              });
        }

        /// <summary>
        /// save file 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public Task SaveAsync(string dirName, string name, byte[] data)
        {
            return Task.Run(() =>
            {
                string fullDir = Path.Combine(GetRootPath(), dirName);
                if (!Directory.Exists(fullDir))
                    Directory.CreateDirectory(fullDir);
                Write(Path.Combine(fullDir, name), data);
            });
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public Stream GetWatermark(string key)
        {
            return Read(Path.Combine(_env.ContentRootPath, WATERMARK_BASE_DIR, key));
        }

        /// <summary>
        /// save watermark
        /// </summary>
        /// <param name="key"></param>
        /// <param name="stream"></param>
        /// <returns></returns>
        public Task SaveWatermark(string key, Stream stream)
        {
            return Task.Run(() =>
            {
                var dir = new DirectoryInfo(Path.Combine(_env.ContentRootPath, WATERMARK_BASE_DIR));
                if (!dir.Exists)
                    dir.Create();
                Write(Path.Combine(dir.FullName, key), stream);
            });
        }


        /// <summary>
        /// Relative to the directory
        /// The URL address md5 is converted into a hexadecimal number by taking 1-4 bits and dividing by 32
        /// </summary>
        /// <param name="originalPicUrl">original_pic url</param>
        /// <returns></returns>
        public string GetDirName(string originalPicUrl)
        {
            if (string.IsNullOrEmpty(originalPicUrl))
                throw new ArgumentNullException($"{nameof(originalPicUrl)} is null");
            string hash = Md5(originalPicUrl);
            int parentDir = Convert.ToInt32("0x" + hash.Substring(0, 4), 16) / 32;
            int secondDir = Convert.ToInt32("0x" + hash.Substring(4, 4), 16) / 32;

            return $"{parentDir}{Path.DirectorySeparatorChar}{secondDir}{Path.DirectorySeparatorChar}{hash.Substring(8)}";
        }




        /// <summary>
        /// get root dir
        /// </summary>
        /// <returns></returns>
        private string GetRootPath()
        {
            return String.IsNullOrEmpty(_config.LocalRoot) ? Path.Combine(_env.ContentRootPath, IMAGE_BASE_DIR) : Path.Combine(_config.LocalRoot);
        }

        /// <summary>
        /// read file
        /// </summary>
        /// <param name="filePath"></param>
        /// <returns></returns>
        private Stream Read(string filePath)
        {
            try
            {
                writeLock.EnterReadLock();
                var file = new FileInfo(filePath);
                if (file.Exists)
                {
                    using (var fs = file.OpenRead())
                    {
                        MemoryStream ms = new MemoryStream();
                        fs.CopyTo(ms);
                        ms.Flush();
                        ms.Position = 0;
                        return ms;
                    }
                }
            }
            finally
            {
                writeLock.ExitReadLock();
            }
            return null;
        }

        /// <summary>
        /// write file
        /// </summary>
        /// <param name="filePath"></param>
        /// <param name="stream"></param>
        private void Write(string filePath, Stream stream)
        {
            var bt = new byte[stream.Length];
            stream.Position = 0;
            stream.Read(bt, 0, bt.Length);
            Write(filePath, bt);
        }

        private void Write(string filePath, byte[] data)
        {
            var file = new FileInfo(filePath);
            try
            {
                writeLock.EnterUpgradeableReadLock();
                writeLock.EnterWriteLock();
                if (!file.Exists)
                {
                    using (var fs = file.Create())
                    {
                        fs.Write(data);
                        fs.Flush();
                    }
                }
            }
            catch
            {
                try
                {
                    if (file.Exists)
                        file.Delete();
                }
                catch
                {

                }
            }
            finally
            {
                writeLock.ExitUpgradeableReadLock();
                writeLock.ExitWriteLock();
            }
        }
    }
}
